﻿<!DOCTYPE html>
<html>
<head>
  <title>GoJS Tree Mapper</title>
  <!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
  <meta name="description" content="A tree mapper diagram to show and edit the relationships between items in two different trees." />
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <script src="../release/go.js"></script>
  <script src="../assets/js/goSamples.js"></script>  <!-- this is only for the GoJS Samples framework -->
  <script id="code">
    function TreeNode() {
      go.Node.call(this);
      this.treeExpandedChanged = function(node) {
        if (node.containingGroup !== null) {
          node.containingGroup.findExternalLinksConnected().each(function(l) { l.invalidateRoute(); });
        }
      };
    }
    go.Diagram.inherit(TreeNode, go.Node);

    TreeNode.prototype.findVisibleNode = function() {
      // redirect links to lowest visible "ancestor" in the tree
      var n = this;
      while (n !== null && !n.isVisible()) {
        n = n.findTreeParentNode();
      }
      return n;
    };
    // end TreeNode

    function MappingLink() {
      go.Link.call(this);
    }
    go.Diagram.inherit(MappingLink, go.Link);

    MappingLink.prototype.getLinkPoint = function(node, port, spot, from, ortho, othernode, otherport) {
      var r = port.getDocumentBounds();
      var group = node.containingGroup;
      var b = (group !== null) ? group.actualBounds : node.actualBounds;
      var op = othernode.getDocumentPoint(go.Spot.Center);
      var x = (op.x > r.centerX) ? b.right : b.left;
      return new go.Point(x, r.centerY);
    };
    // end MappingLink

    function init() {
      if (window.goSamples) goSamples();  // init for these samples -- you don't need to call this
      var $ = go.GraphObject.make;  // for conciseness in defining templates

      myDiagram =
        $(go.Diagram, "myDiagramDiv",
          {
            "commandHandler.copiesTree": true,
            "commandHandler.deletesTree": true,
            // newly drawn links always map a node in one tree to a node in another tree
            "linkingTool.archetypeLinkData": { category: "Mapping" },
            "linkingTool.linkValidation": checkLink,
            "relinkingTool.linkValidation": checkLink,
            "undoManager.isEnabled": true,
            "ModelChanged": function(e) {
              if (e.isTransactionFinished) {  // show the model data in the page's TextArea
                document.getElementById("mySavedModel").textContent = e.model.toJson();
              }
            }
          });

      // All links must go from a node inside the "Left Side" Group to a node inside the "Right Side" Group.
      function checkLink(fn, fp, tn, tp, link) {
        // make sure the nodes are inside different Groups
        if (fn.containingGroup === null || fn.containingGroup.data.key !== -1) return false;
        if (tn.containingGroup === null || tn.containingGroup.data.key !== -2) return false;
        //// optional limit to a single mapping link per node
        //if (fn.linksConnected.any(function(l) { return l.category === "Mapping"; })) return false;
        //if (tn.linksConnected.any(function(l) { return l.category === "Mapping"; })) return false;
        return true;
      }

      // Each node in a tree is defined using the default nodeTemplate.
      myDiagram.nodeTemplate =
        $(TreeNode,
          { movable: false, copyable: false, deletable: false },  // user cannot move an individual node
          // no Adornment: instead change panel background color by binding to Node.isSelected
          { selectionAdorned: false },
          // whether the user can start drawing a link from or to this node depends on which group it's in
          new go.Binding("fromLinkable", "group", function(k) { return k === -1; }),
          new go.Binding("toLinkable", "group", function(k) { return k === -2; }),
          $("TreeExpanderButton",  // support expanding/collapsing subtrees
            {
              width: 14, height: 14,
              "ButtonIcon.stroke": "white",
              "ButtonIcon.strokeWidth": 2,
              "ButtonBorder.fill": "goldenrod",
              "ButtonBorder.stroke": null,
              "ButtonBorder.figure": "Rectangle",
              "_buttonFillOver": "darkgoldenrod",
              "_buttonStrokeOver": null,
              "_buttonFillPressed": null
            }),
          $(go.Panel, "Horizontal",
            { position: new go.Point(16, 0) },
            new go.Binding("background", "isSelected", function(s) { return (s ? "lightblue" : "white"); }).ofObject(),
            //// optional icon for each tree node
            //$(go.Picture,
            //  { width: 14, height: 14,
            //    margin: new go.Margin(0, 4, 0, 0),
            //    imageStretch: go.GraphObject.Uniform,
            //    source: "images/defaultIcon.png" },
            //  new go.Binding("source", "src")),
            $(go.TextBlock,
              new go.Binding("text", "key", function(s) { return "item " + s; }))
          )  // end Horizontal Panel
        );  // end Node

      // These are the links connecting tree nodes within each group.

      myDiagram.linkTemplate = $(go.Link);  // without lines

      myDiagram.linkTemplate =  // with lines
        $(go.Link,
          {
            selectable: false,
            routing: go.Link.Orthogonal,
            fromEndSegmentLength: 4,
            toEndSegmentLength: 4,
            fromSpot: new go.Spot(0.001, 1, 7, 0),
            toSpot: go.Spot.Left
          },
          $(go.Shape,
            { stroke: "lightgray" }));

      // These are the blue links connecting a tree node on the left side with one on the right side.
      myDiagram.linkTemplateMap.add("Mapping",
        $(MappingLink,
          { isTreeLink: false, isLayoutPositioned: false, layerName: "Foreground" },
          { fromSpot: go.Spot.Right, toSpot: go.Spot.Left },
          { relinkableFrom: true, relinkableTo: true },
          $(go.Shape, { stroke: "blue", strokeWidth: 2 })
        ));

      myDiagram.groupTemplate =
        $(go.Group, "Auto",
          new go.Binding("position", "xy", go.Point.parse).makeTwoWay(go.Point.stringify),
          {
            deletable: false,
            layout:
              $(go.TreeLayout,
                {
                  alignment: go.TreeLayout.AlignmentStart,
                  angle: 0,
                  compaction: go.TreeLayout.CompactionNone,
                  layerSpacing: 16,
                  layerSpacingParentOverlap: 1,
                  nodeIndentPastParent: 1.0,
                  nodeSpacing: 0,
                  setsPortSpot: false,
                  setsChildPortSpot: false
                })
          },
          $(go.Shape, { fill: "white", stroke: "lightgray" }),
          $(go.Panel, "Vertical",
            { defaultAlignment: go.Spot.Left },
            $(go.TextBlock,
              { font: "bold 14pt sans-serif", margin: new go.Margin(5, 5, 0, 5) },
              new go.Binding("text")),
            $(go.Placeholder, { padding: 5 })
          )
        );

      var nodeDataArray = [
        { isGroup: true, key: -1, text: "Left Side", xy: "0 0" },
        { isGroup: true, key: -2, text: "Right Side", xy: "300 0" }
      ];
      var linkDataArray = [
        { from: 6, to: 1012, category: "Mapping" },
        { from: 4, to: 1006, category: "Mapping" },
        { from: 9, to: 1004, category: "Mapping" },
        { from: 1, to: 1009, category: "Mapping" },
        { from: 14, to: 1010, category: "Mapping" }
      ];

      // initialize tree on left side
      var root = { key: 0, group: -1 };
      nodeDataArray.push(root);
      for (var i = 0; i < 11;) {
        i = makeTree(3, i, 17, nodeDataArray, linkDataArray, root, -1, root.key);
      }

      // initialize tree on right side
      root = { key: 1000, group: -2 };
      nodeDataArray.push(root);
      for (var i = 0; i < 15;) {
        i = makeTree(3, i, 15, nodeDataArray, linkDataArray, root, -2, root.key);
      }
      myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
    }

    // help create a random tree structure
    function makeTree(level, count, max, nodeDataArray, linkDataArray, parentdata, groupkey, rootkey) {
      var numchildren = Math.floor(Math.random() * 10);
      for (var i = 0; i < numchildren; i++) {
        if (count >= max) return count;
        count++;
        var childdata = { key: rootkey + count, group: groupkey };
        nodeDataArray.push(childdata);
        linkDataArray.push({ from: parentdata.key, to: childdata.key });
        if (level > 0 && Math.random() > 0.5) {
          count = makeTree(level - 1, count, max, nodeDataArray, linkDataArray, childdata, groupkey, rootkey);
        }
      }
      return count;
    }
  </script>
</head>
<body onload="init()">
<div id="sample">
  <div id="myDiagramDiv" style="border: 1px solid black; width: 700px; height: 350px"></div>
  <p>
    This sample is like the <a href="records.html">Mapping Fields of Records</a> sample,
    but it has a collapsible tree of nodes on each side, rather than a simple list of items.
    The implementation of the trees comes from the <a href="treeView.html">Tree View</a> sample.
  </p>
  <p>
    Draw new links by dragging from any field (i.e. any tree node).
    Reconnect a selected link by dragging its diamond-shaped handle.
  </p>
  <p>The model data, automatically updated after each change or undo or redo:</p>
  <textarea id="mySavedModel" style="width:100%;height:300px"></textarea>
</div>
</body>
</html>
